/*
 * Copyright 2012-2013 Finbudget.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package finbudgettable.client.ui.grid;

import com.google.inject.Inject;
import com.google.web.bindery.event.shared.EventBus;

import finbudget.client.general.OraclesManager;
import finbudget.client.lists.ListsManager;
import finbudget.client.permissions.PermissionPresenter;
import finbudget.client.ui.table.Field;
import finbudget.client.ui.table.LinkField;
import finbudget.client.ui.table.LinksField;
import finbudget.client.ui.table.fields.PermissionField;
import finbudget.client.ui.widget.upload.UploadWidget;
import finbudgettable.client.ui.grid.cell.attachment.AttachmentColumn;
import finbudgettable.client.ui.grid.cell.bool.BoolColumn;
import finbudgettable.client.ui.grid.cell.currency.CurrencyColumn;
import finbudgettable.client.ui.grid.cell.date.DateColumn;
import finbudgettable.client.ui.grid.cell.datetime.DateTimeColumn;
import finbudgettable.client.ui.grid.cell.description.DescriptionColumn;
import finbudgettable.client.ui.grid.cell.link.LinkColumn;
import finbudgettable.client.ui.grid.cell.links.LinksColumn;
import finbudgettable.client.ui.grid.cell.list.ListColumn;
import finbudgettable.client.ui.grid.cell.longg.LongColumn;
import finbudgettable.client.ui.grid.cell.permission.PermissionColumn;
import finbudgettable.client.ui.grid.cell.staticc.StaticColumn;
import finbudgettable.client.ui.grid.cell.string.StringCell;
import finbudgettable.client.ui.grid.cell.string.StringColumn;
import finbudgettable.client.ui.grid.cell.suggest.SuggestColumn;
import finbudgettable.client.ui.grid.cell.tripple.TrippleColumn;
import finbudgettable.client.ui.grid.column.GridColumn;
import finbudgettable.client.ui.grid.column.GridColumn.Builder;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static finbudget.client.lists.GetListOfItems.ListType;

public class GridFactory {

    interface ColumnFactory {
        GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals);
    }

    private final Map<Field.Type, ColumnFactory> columnFactories = new HashMap<Field.Type, ColumnFactory>();

    private class StaticColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return StaticColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class SuggestColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return SuggestColumn.builder(grid, field.id, oraclesManager, field.listTypeGrid, hasTotals, eventBus)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class ListColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return ListColumn.builder(grid, field.id, listsManager, field.listTypeGrid, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class AttachmentColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return AttachmentColumn.builder(grid, field.id, uploadWidgetFactory, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class CurrencyColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return CurrencyColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class BoolColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return BoolColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class TrippleColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return TrippleColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class DateColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return DateColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class DateTimeColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return DateTimeColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class DescriptionColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return DescriptionColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class LongColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            return LongColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setNullable(field.required == null)
                    .setWidth(field.getWidth())
                    .build();
        }
    }

    private class StringColumnFactory implements ColumnFactory {
        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
        	Builder<StringCell, String> builder = StringColumn.builder(grid, field.id, hasTotals)
                    .setCaption(field.captionGrid)
                    .setReadonly(field.readOnly)
                    .setWidth(field.getWidth())
                    .setNullable(field.required == null);
            return builder.build();
        }
    }

    private class LinkColumnFactory implements ColumnFactory {

        private final StaticColumnFactory staticColumnFactory;

        private LinkColumnFactory(StaticColumnFactory staticColumnFactory) {
            this.staticColumnFactory = staticColumnFactory;
        }

        @Override
        public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
            if (field instanceof LinkField) {
                return LinkColumn.builder(grid, (LinkField) field, hasTotals)
                        .setCaption(field.captionGrid)
                        .setWidth(field.getWidth())
                        .build();
            } else {
                return staticColumnFactory.create(grid, field, hasTotals);
            }
        }
    }

    private class PermissionColumnFactory implements ColumnFactory {

		@Override
		public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
			if (field != null && field instanceof PermissionField) {
				return PermissionColumn.builder(grid, (PermissionField) field, permissionsPresenter, hasTotals).build();
			}
			return null;
		}

    }

    private class LinksColumnFactory implements ColumnFactory {

		@Override
		public GridColumn<?> create(CellGrid grid, Field field, boolean hasTotals) {
			if (field != null && field instanceof LinksField) {
				return LinksColumn.builder(grid, (LinksField) field, hasTotals).setCaption(field.captionGrid).build();
			}
			return null;
		}

    }

    private final OraclesManager oraclesManager;
    private final ListsManager listsManager;
    private UploadWidget.UploadWidgetFactory uploadWidgetFactory;
    private final PermissionPresenter permissionsPresenter;
    private final EventBus eventBus;

    @Inject
    public GridFactory(OraclesManager oraclesManager, ListsManager listsManager, UploadWidget.UploadWidgetFactory uploadWidgetFactory,
                       PermissionPresenter permissionsPresenter, EventBus eventBus) {
        this.oraclesManager = oraclesManager;
        this.listsManager = listsManager;
        this.uploadWidgetFactory = uploadWidgetFactory;
        this.permissionsPresenter = permissionsPresenter;
        this.eventBus = eventBus;

        StaticColumnFactory staticColumnFactory = new StaticColumnFactory();
        columnFactories.put(Field.Type.STATIC, staticColumnFactory);
        columnFactories.put(Field.Type.ATTACHMENT, new AttachmentColumnFactory());
        columnFactories.put(Field.Type.BIGDECIMAL, new CurrencyColumnFactory());
        columnFactories.put(Field.Type.BOOL, new BoolColumnFactory());
        columnFactories.put(Field.Type.TRIPPLE, new TrippleColumnFactory());
        columnFactories.put(Field.Type.DATE, new DateColumnFactory());
        columnFactories.put(Field.Type.DATETIME, new DateTimeColumnFactory());
        columnFactories.put(Field.Type.LONG, new LongColumnFactory());
        columnFactories.put(Field.Type.STRING, new StringColumnFactory());

        SuggestColumnFactory suggestColumnFactory = new SuggestColumnFactory();
        columnFactories.put(Field.Type.SUGGEST, suggestColumnFactory);
        columnFactories.put(Field.Type.SUGGESTADDABLE, suggestColumnFactory);

        ListColumnFactory listColumnFactory = new ListColumnFactory();
        columnFactories.put(Field.Type.LIST, listColumnFactory);
        columnFactories.put(Field.Type.LISTADDABLE, listColumnFactory);

        DescriptionColumnFactory descriptionColumnFactory = new DescriptionColumnFactory();
        columnFactories.put(Field.Type.DESCRIPTION, descriptionColumnFactory);
        columnFactories.put(Field.Type.DESCRIPTIONINPLACE, descriptionColumnFactory);

        columnFactories.put(Field.Type.LINK, new LinkColumnFactory(staticColumnFactory));
        columnFactories.put(Field.Type.PERMISSION, new PermissionColumnFactory());
        columnFactories.put(Field.Type.LINKS, new LinksColumnFactory());
    }

    public void createColumns(CellGrid grid, List<Field> fields, boolean hasTotals) {
    	createColumns(grid, fields, hasTotals, null);
    }

    public void createColumns(CellGrid grid, List<Field> fields, boolean hasTotals, String sortFieldId) {
    	if (grid != null && fields != null) {
    		grid.createStatusColumn(hasTotals);
	        for (Field field : fields) {
	            GridColumn<?> column = createColumn(grid, field, hasTotals);
	            if (column != null && field.isHiddenByDefault()) {
	            	grid.hideColumn(column);
	            }
	        }
	    	if (sortFieldId != null) {
	    		GridColumn<?> column = grid.getColumnByFieldName(sortFieldId);
	    		if (column != null) {
	    			grid.sortColumn(column);
	    		}
	    	}
    	}
    }

    /**
     * @param grid  a target grid
     * @param field an object containing column's properties
     * @param hasTotals 
     * @return a create column or null if column was not created
     */
    public GridColumn<?> createColumn(CellGrid grid, Field field, boolean hasTotals) {
        ColumnFactory factory = columnFactories.get(field.type);
        if (factory != null) {
            return factory.create(grid, field, hasTotals);
        }
        return null;
    }

    public CellGrid createGrid(ChangeHandler changeHandler) {
        return new CellGrid(changeHandler);
    }

    public StaticColumn.Builder staticBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return StaticColumn.builder(grid, fieldName, hasTotals);
    }

    public SuggestColumn.Builder suggestBuilder(CellGrid grid, String fieldName, ListType listType, boolean hasTotals) {
        return SuggestColumn.builder(grid, fieldName, oraclesManager, listType, hasTotals, eventBus);
    }

    public ListColumn.Builder listBuilder(CellGrid grid, String fieldName, ListType listType, boolean hasTotals) {
        return ListColumn.builder(grid, fieldName, listsManager, listType, hasTotals);
    }

    public AttachmentColumn.Builder attachmentBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return AttachmentColumn.builder(grid, fieldName, uploadWidgetFactory, hasTotals);
    }

    public CurrencyColumn.Builder currencyBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return CurrencyColumn.builder(grid, fieldName, hasTotals);
    }

    public BoolColumn.Builder boolBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return BoolColumn.builder(grid, fieldName, hasTotals);
    }

    public DateColumn.Builder dateBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return DateColumn.builder(grid, fieldName, hasTotals);
    }

    public DateTimeColumn.Builder dateTimeBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return DateTimeColumn.builder(grid, fieldName, hasTotals);
    }

    public DescriptionColumn.Builder descriptionBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return DescriptionColumn.builder(grid, fieldName, hasTotals);
    }

    public LongColumn.Builder longBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return LongColumn.builder(grid, fieldName, hasTotals);
    }

    public StringColumn.Builder stringBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return StringColumn.builder(grid, fieldName, hasTotals);
    }

    public TrippleColumn.Builder trippleBuilder(CellGrid grid, String fieldName, boolean hasTotals) {
        return TrippleColumn.builder(grid, fieldName, hasTotals);
    }

    public LinkColumn.Builder linkBuilder(CellGrid grid, LinkField linkField, boolean hasTotals) {
        return LinkColumn.builder(grid, linkField, hasTotals);
    }

//    public DeleteColumn.Builder infoDeleteBuilder(CellGrid grid, boolean hasTotals) {
//        return DeleteColumn.builder(grid, hasTotals);
//    }
//
//    public InfoColumn.Builder infoBuilder(CellGrid grid, boolean hasTotals) {
//        return InfoColumn.builder(grid, hasTotals);
//    }

}
